#!/bin/bash
set -eo pipefail

# Get the root mtd device number (mtdX) from "/dev/ubiblockX_Y on /"
findrootmtd() {
  rootmatch=" on / "
  m="$(mount | grep "${rootmatch}" | grep "ubiblock")"
  m="${m##*ubiblock}"
  m="${m%_*}"
  if [ -z "${m}" ]; then
    # default to bmc mtd (0)
    m=0
  fi
  echo "mtd${m}"
}

findrootubi() {
  rootmatch=" on / "
  m="$(mount | grep "${rootmatch}")"
  m="${m##*ubiblock}"
  m="${m% on*}"
  echo "ubi${m}"
}

# Get the mtd device number (mtdX)
findmtd() {
  m="$(grep -xl "$1" /sys/class/mtd/*/name)"
  m="${m%/name}"
  m="${m##*/}"
  echo "${m}"
}

# Get the mtd device number only (return X of mtdX)
findmtdnum() {
  m="$(findmtd "$1")"
  m="${m##mtd}"
  echo "${m}"
}

# Get the ubi device number (ubiX_Y)
findubi() {
  u="$(grep -xl "$1" /sys/class/ubi/ubi?/subsystem/ubi*/name)"
  u="${u%/name}"
  u="${u##*/}"
  echo "${u}"
}

# Get the ubi device number (ubiX_Y) on a specific mtd
findubi_onmtd() {
  u="$(grep -xl "$1" /sys/class/ubi/ubi"$2"/subsystem/ubi"$2"*/name)"
  u="${u%/name}"
  u="${u##*/}"
  echo "${u}"
}

# Get all ubi device names on a specific mtd that match requested string
findubiname_onmtd() {
  u="$(grep -h "$1" /sys/class/ubi/ubi"$2"/subsystem/ubi"$2"*/name)"
  u="${u%/name}"
  u="${u##*/}"
  echo "${u}"
}

# Get the name from the requested ubiX_Y volume
findname() {
  n="$(cat /sys/class/ubi/$1/name)"
  echo "${n}"
}

# Set the u-boot envs that perform a side switch on failure to boot
set_wdt2bite() {
  if ! fw_printenv wdt2bite 2>/dev/null; then
    fw_setenv wdt2bite "mw.l 0x1e785024 0xa 1; mw.b 0x1e78502c 0xb3 1"
    fw_setenv bootalt "run wdt2bite"
    fw_setenv obmc_bootcmd "ubi part obmc-ubi; run do_rwreset; ubi read \
\${loadaddr} \${kernelname}; bootm \${loadaddr} || run bootalt"
  fi
}

# Make space on flash before creating new volumes. This can be enhanced
# determine current flash usage. For now only keep a "keepmax" number of them
ubi_remove_volumes()
{
  rootubi="$(findrootubi)"
  rootname="$(findname "${rootubi}")"
  rootversion="${rootname##*-}"
  rootkernel="kernel-${rootversion}"

  # Just keep max number of volumes before updating, don't delete the version
  # the BMC is booted from, and when a version is identified to be deleted,
  # delete both the rofs and kernel volumes for that version.
  rmnames="$(findubiname_onmtd "${name%-*}-" "${ro}")"
  rmnames=(${rmnames})
  ubicount="${#rmnames[@]}"
  while [ ${ubicount} -ge ${keepmax} ]; do
    # Loop through existing volumes and skip currently active ones
    for (( index=0; index<${#rmnames[@]}; index++ )); do
      rmname="${rmnames[${index}]}"
      rmversion="${rmname##*-}"
      [ "${rmversion}" == "${version}" ] && continue
      rmubi="$(findubi_onmtd "rofs-${rmversion}" "${ro}")"
      if [[ ( "${rmubi}" != "${rootubi}" ) &&
            ( "${rmname}" != "${rootkernel}" ) ]]; then
        ubi_remove "rofs-${rmversion}" "${ro}"
        ubi_remove "kernel-${rmversion}" "${ro}"
        # Remove priority value
        fw_setenv "${rmversion}"
        break
      fi
    done
    # Decrease count regardless to avoid an infinite loop
    (( ubicount-- ))
  done
}

ubi_rw() {
  rwmtd="$(findmtd "${reqmtd}")"
  rw="${rwmtd#mtd}"
  ubidev="/dev/ubi${rw}"

  # Update rwfs_size, check imgsize was specified, otherwise it'd clear the var
  if [ ! -z "$imgsize" ]; then
    rwsize="$(fw_printenv -n rwfs_size 2>/dev/null)" || true
    if [[ "${imgsize}" != "${rwsize}" ]]; then
      fw_setenv rwfs_size "${imgsize}"
    fi
  fi

  vol="$(findubi "${name}")"
  if [ -z "${vol}" ]; then
    ubimkvol "${ubidev}" -N "${name}" -s "${imgsize}"
  fi
}

ubi_ro() {
  keepmax=2 # Default 2 volumes per mtd
  romtd="$(findmtd "${reqmtd}")"
  romtd2="$(findmtd "${reqmtd2}")"

  if [ ! "${romtd}" == "${romtd2}" ]; then
    # Request to use alternate mtd device, choose the non-root one
    keepmax=1 # 1 volume on each of the requested mtds
    rootmtd="$(findrootmtd)"
    if [ "${rootmtd}" == "${romtd}" ]; then
      romtd="${romtd2}"
    fi
  fi
  ro="${romtd#mtd}"
  ubidev="/dev/ubi${ro}"

  ubi_remove_volumes

  if [ -z "${imgfile}" ]; then
    echo "Unable to create read-only volume. Image file not specified."
    return 1
  fi

  # Create a ubi volume, dynamically sized to fit BMC image if size unspecified
  img="/tmp/images/${version}/${imgfile}"
  imgsize="$(stat -c '%s' ${img})"

  vol="$(findubi "${name}")"
  if [ ! -z "${vol}" ]; then
    # Allow a duplicate kernel volume on the alt mtd
    if [[ "${name}" =~ "kernel" ]]; then
      vol="$(findubi_onmtd "${name}" "${ro}")"
    fi
  fi
  if [ -z "${vol}" ]; then
    ubimkvol "${ubidev}" -N "${name}" -s "${imgsize}" --type=static
    vol="$(findubi "${name}")"
  fi
}

# Squashfs images need a ubi block
ubi_block() {
  vol="$(findubi "${name}")"
  ubidevid="${vol#ubi}"
  block="/dev/ubiblock${ubidevid}"
  if [ ! -e "$block" ]; then
    ubiblock --create "/dev/ubi${ubidevid}"
  fi
}

ubi_updatevol() {
  vol="$(findubi "${name}")"
  ubidevid="${vol#ubi}"
  img="/tmp/images/${version}/${imgfile}"
  ubiupdatevol "/dev/ubi${ubidevid}" "${img}"
}

ubi_remove() {
    rmname="$1"
    rmmtd="$2"
    if [ ! -z "${rmmtd}" ]; then
      vol="$(findubi_onmtd "${rmname}" "${rmmtd}")"
    else
      vol="$(findubi "${rmname}")"
    fi

    if [ ! -z "$vol" ]; then
        vol="${vol%_*}"

        if grep -q "$rmname" /proc/mounts; then
            mountdir=$(grep "$rmname" /proc/mounts | cut -d " " -f 2)
            umount "$mountdir"
            rm -r "$mountdir"
        fi

        ubirmvol "/dev/${vol}" -N "$rmname"
    fi
}

ubi_cleanup() {
    # When ubi_cleanup is run, it expects one or no active version.
    activeVersion=$(busctl --list --no-pager tree \
            xyz.openbmc_project.Software.BMC.Updater | \
            grep /xyz/openbmc_project/software/ | tail -c 9)

    if [[ -z "$activeVersion" ]]; then
        vols=$(ubinfo -a | grep "rofs-" | cut -c 14-)
        vols=(${vols})
    else
        vols=$(ubinfo -a | grep "rofs-" | \
                grep -v "$activeVersion" | cut -c 14-)
        vols=(${vols})
    fi

    for (( index=0; index<${#vols[@]}; index++ )); do
         ubi_remove ${vols[index]}
    done
}

mount_alt_rwfs() {
  altNum="$(findmtdnum "alt-bmc")"
  if [ ! -z "${altNum}" ]; then
    altRwfs=$(ubinfo -a -d ${altNum} | grep -w "rwfs") || true
    if [ ! -z "${altRwfs}" ]; then
      altVarMount="/media/alt/var"
      mkdir -p "${altVarMount}"
      if mount ubi"${altNum}":rwfs "${altVarMount}" -t ubifs -o defaults; then
        mkdir -p "${altVarMount}"/persist/etc
      fi
    fi
  fi
}

remount_ubi() {
  bmcmtd="$(findmtd "bmc")"
  altbmcmtd="$(findmtd "alt-bmc")"
  mtds="${bmcmtd: -1}","${altbmcmtd: -1}"

  IFS=',' read -r -a mtds <<< "$mtds"
  mtds=($(echo "${mtds[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))
  for mtd in ${mtds[@]}; do
    # Get information on all ubi volumes
    ubinfo=$(ubinfo -d ${mtd})
    presentVolumes=${ubinfo##*:}
    IFS=', ' read -r -a array <<< "$presentVolumes"
    for element in ${array[@]}; do
      elementProperties=$(ubinfo -d $mtd -n $element)
      # Get ubi volume name by getting rid of additional properties
      name=${elementProperties#*Name:}
      name="${name%Character*}"
      name="$(echo -e "${name}" | tr -d '[:space:]')"

      if [[ ${name} == rofs-* ]]; then
        mountdir="/media/${name}"

        if [ ! -d ${mountdir} ]; then
          mkdir -p "${mountdir}"
          # U-Boot will create the ubiblock for the running version, but not
          # for the version on the other chip
          if [ ! -e "/dev/ubiblock${mtd}_${element}" ]; then
            ubiblock --create /dev/ubi${mtd}_${element}
          fi
          mount -t squashfs -o ro "/dev/ubiblock${mtd}_${element}" "${mountdir}"
        fi
      fi
    done
  done

  set_wdt2bite
}

# Read the current env variable and set it on the alternate boot env
copy_env_var_to_alt() {
  varName=$1
  value="$(fw_printenv -n "${varName}")"
  fw_setenv -c /etc/alt_fw_env.config "${varName}" "${value}"
}

# When the alternate bmc chip boots, u-boot thinks its the primary mtdX.
# Therefore need to swap the chip numbers when copying the ubiblock and root to
# alternate bmc u-boot environment.
copy_ubiblock_to_alt() {
  value="$(fw_printenv -n ubiblock)"
  bmcNum="$(findmtdnum "bmc")"
  altNum="$(findmtdnum "alt-bmc")"
  replaceAlt="${value/${altNum},/${bmcNum},}"

  if [[ "${value}" == "${replaceAlt}" ]]; then
    replaceBmc="${value/${bmcNum},/${altNum},}"
    value=${replaceBmc}
  else
    value=${replaceAlt}
  fi

  fw_setenv -c /etc/alt_fw_env.config ubiblock "${value}"
}

copy_root_to_alt() {
  value="$(fw_printenv -n root)"
  bmcNum="$(findmtdnum "bmc")"
  altNum="$(findmtdnum "alt-bmc")"
  replaceAlt="${value/${altNum}_/${bmcNum}_}"

  if [[ "${value}" == "${replaceAlt}" ]]; then
    replaceBmc="${value/${bmcNum}_/${altNum}_}"
    value=${replaceBmc}
  else
    value=${replaceAlt}
  fi

  fw_setenv -c /etc/alt_fw_env.config root "${value}"
}

ubi_setenv() {
    # The U-Boot environment maintains two banks of environment variables.
    # The banks need to be consistent with each other to ensure that these
    # variables can reliably be read from file. In order to guarantee that the
    # banks are both correct, we need to run fw_setenv twice.
    variable=$1
    if [[ "$variable" == *"="* ]]; then
        varName="${variable%=*}"
        value="${variable##*=}"
        # Write only if var is not set already to the requested value
        currentValue="$(fw_printenv -n "${varName}" 2>/dev/null)" || true
        if [[ "${currenValue}" != "${value}" ]]; then
          fw_setenv "$varName" "$value"
          fw_setenv "$varName" "$value"
        fi
    else
        fw_setenv "$variable"
        fw_setenv "$variable"
    fi
}

mtd_write() {
  flashmtd="$(findmtd "${reqmtd}")"
  img="/tmp/images/${version}/${imgfile}"
  flashcp -v ${img} /dev/${flashmtd}
}

backup_env_vars() {
  copy_env_var_to_alt kernelname
  copy_ubiblock_to_alt
  copy_root_to_alt
}

update_env_vars() {
  vol="$(findubi rofs-"${version}")"
  if [ -z "${vol}" ]; then
    return 1
  fi
  ubidevid="${vol#ubi}"
  block="/dev/ubiblock${ubidevid}"
  if [ ! -e "${block}" ]; then
    return 1
  fi
  ubi_setenv "kernelname=kernel-${version}"
  ubi_setenv "ubiblock=$(echo "${ubidevid}" | sed 's/_/,/')"
  ubi_setenv "root=${block}"
}

#TODO: Replace the implementation with systemd-inhibitors lock
#      once systemd/systemd#949 is resolved
rebootguardenable() {
  dir="/run/systemd/system/"
  file="reboot-guard.conf"
  units=("reboot" "poweroff" "halt")

  for unit in "${units[@]}"; do
    mkdir -p ${dir}${unit}.target.d
    echo -e "[Unit]\nRefuseManualStart=yes" >> ${dir}${unit}.target.d/${file}
  done
}

#TODO: Replace the implementation with systemd-inhibitors lock
#      once systemd/systemd#949 is resolved
rebootguarddisable() {
  dir="/run/systemd/system/"
  file="reboot-guard.conf"
  units=("reboot" "poweroff" "halt")

  for unit in "${units[@]}"; do
    rm -rf ${dir}${unit}.target.d/${file}
  done
}

# Create a copy in the alt mtd
create_vol_in_alt() {
  alt="alt-bmc"
  altmtd="$(findmtd "${alt}")"
  if [ ! -z "${altmtd}" ]; then
    reqmtd="${alt}"
    reqmtd2="${alt}"
    ubi_ro
    ubi_updatevol
  fi
}

# Copy contents of one MTD device to another
mtd_copy() {
  in=$1
  out=$2

  # Must erase MTD first to prevent corruption
  flash_eraseall "${out}"
  dd if="${in}" of="${out}"
}

mirroruboot() {
  bmc="$(findmtd "u-boot")"
  bmcdev="/dev/${bmc}"
  alt="$(findmtd "alt-u-boot")"
  altdev="/dev/${alt}"

  checksum_bmc="$(md5sum "${bmcdev}")"
  checksum_bmc="${checksum_bmc% *}"
  checksum_alt="$(md5sum "${altdev}")"
  checksum_alt="${checksum_alt% *}"

  if [[ "${checksum_bmc}" != "${checksum_alt}" ]]; then
    bmcenv="$(findmtd "u-boot-env")"
    bmcenvdev="/dev/${bmcenv}"
    altenv="$(findmtd "alt-u-boot-env")"
    altenvdev="/dev/${altenv}"

    echo "Mirroring U-boot to alt chip"
    mtd_copy "${bmcdev}" "${altdev}"
    mtd_copy "${bmcenvdev}" "${altenvdev}"

    copy_ubiblock_to_alt
    copy_root_to_alt
  fi
}

case "$1" in
  mtduboot)
    reqmtd="$2"
    version="$3"
    imgfile="image-u-boot"
    mtd_write
    ;;
  ubirw)
    reqmtd="$2"
    name="$3"
    imgsize="$4"
    ubi_rw
    ;;
  ubiro)
    reqmtd="$(echo "$2" | cut -d "+" -f 1)"
    reqmtd2="$(echo "$2" | cut -d "+" -f 2)"
    name="$3"
    version="$4"
    imgfile="image-rofs"
    ubi_ro
    ubi_updatevol
    ubi_block
    ;;
  ubikernel)
    reqmtd="$(echo "$2" | cut -d "+" -f 1)"
    reqmtd2="$(echo "$2" | cut -d "+" -f 2)"
    name="$3"
    version="$4"
    imgfile="image-kernel"
    ubi_ro
    ubi_updatevol
    create_vol_in_alt
    ;;
  ubiremove)
    name="$2"
    ubi_remove "${name}"
    ;;
  ubicleanup)
    ubi_cleanup
    ;;
  ubisetenv)
    ubi_setenv "$2"
    ;;
  ubiremount)
    remount_ubi
    mount_alt_rwfs
    ;;
  createenvbackup)
    backup_env_vars
    ;;
  updateubootvars)
    version="$2"
    update_env_vars
    ;;
  rebootguardenable)
    rebootguardenable
    ;;
  rebootguarddisable)
    rebootguarddisable
    ;;
  mirroruboot)
    mirroruboot
    ;;
  *)
    echo "Invalid argument"
    exit 1
    ;;
esac
